package hot.job.autoconfigure.scanner;

import hot.job.core.annotation.HotJob;
import hot.job.core.holder.HotJobMetaHolder;
import hot.job.core.job.StoppableJob;
import hot.job.core.meta.JobMeta;
import org.quartz.Job;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.*;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * scan hot jobs to hold their meta info
 * only scans class info, don't do register
 * @author gaowenhao
 * @since 2022/7/11 10:12
 */
public class HotJobScanner extends ClassPathBeanDefinitionScanner {

    private final BeanDefinitionRegistry registry;

    private final BeanNameGenerator beanNameGenerator;

    private final ScopeMetadataResolver scopeMetadataResolver;


    public HotJobScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        super(registry, useDefaultFilters);
        this.registry = registry;
        this.beanNameGenerator = AnnotationBeanNameGenerator.INSTANCE;
        this.scopeMetadataResolver = new AnnotationScopeMetadataResolver();
    }


    @NonNull
    @Override
    protected Set<BeanDefinitionHolder> doScan(@NonNull String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidates = super.findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    this.postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                if (super.checkCandidate(beanName, candidate)) {
                    ScannedGenericBeanDefinition sbd = (ScannedGenericBeanDefinition) candidate;
                    String jobClassName = sbd.getBeanClassName();
                    Class<? extends Job> jobClass = tryResolveJobClass(jobClassName);
                    if(jobClass != null){
                        HotJob hot = jobClass.getAnnotation(HotJob.class);
                        JobMeta meta = JobMeta.builder().name(hot.name()).group(hot.group()).hot(hot.hot()).jobClass(jobClass).cron(hot.cron()).build();
                        String key = meta.getGroup() + "." + meta.getName();
                        HotJobMetaHolder.hold(key, meta);
                    }
                }
            }
        }
        return beanDefinitions;
    }


    private Class<? extends Job> tryResolveJobClass(String jobClassName){
        try {
            Class<?> clazz = ClassUtils.forName(jobClassName, getClass().getClassLoader());
            if(clazz.getSuperclass().equals(StoppableJob.class) && clazz.isAnnotationPresent(HotJob.class)){
                return (Class<? extends Job>)clazz;
            }
            return null;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

}
